import {
  format,
  complementError,
  asyncMap,
  warning,
  deepMerge
} from "./util.js";
import validators from "./validator/index.js";
import {
  messages as defaultMessages,
  newMessages
} from "./messages.js";

function Schema(e) {
  this.rules = null, this._messages = defaultMessages, this.define(e)
}
Schema.prototype = {
  messages(e) {
    return e && (this._messages = deepMerge(newMessages(), e)), this._messages
  },
  define(e) {
    if (!e) throw new Error("Cannot configure a schema with no rules");
    if ("object" != typeof e || Array.isArray(e)) throw new Error("Rules must be an object");
    let t, s;
    for (t in this.rules = {}, e) e.hasOwnProperty(t) && (s = e[t], this.rules[t] = Array.isArray(s) ? s : [s])
  },
  validate(e, t = {}, s) {
    let r, a, o = e,
      i = t,
      n = s;
    if ("function" == typeof i && (n = i, i = {}), !this.rules || 0 === Object.keys(this.rules).length) return void(n && n());
    if (i.messages) {
      let e = this.messages();
      e === defaultMessages && (e = newMessages()), deepMerge(e, i.messages), i.messages = e
    } else i.messages = this.messages();
    const l = {};
    (i.keys || Object.keys(this.rules)).forEach(t => {
      r = this.rules[t], a = o[t], r.forEach(s => {
        let r = s;
        "function" == typeof r.transform && (o === e && (o = {
          ...o
        }), a = o[t] = r.transform(a)), r = "function" == typeof r ? {
          validator: r
        } : {
          ...r
        }, r.validator = this.getValidationMethod(r), r.field = t, r.fullField = r.fullField || t, r.type = this.getType(r), r.validator && (l[t] = l[t] || [], l[t].push({
          rule: r,
          value: a,
          source: o,
          field: t
        }))
      })
    });
    const f = {};
    asyncMap(l, i, (e, t) => {
      const s = e.rule;
      let r = !("object" !== s.type && "array" !== s.type || "object" != typeof s.fields && "object" != typeof s.defaultField);

      function a(e, t) {
        return {
          ...t,
          fullField: `${s.fullField}.${e}`
        }
      }

      function o(o = []) {
        let n = o;
        if (Array.isArray(n) || (n = [n]), n.length && warning("async-validator:", n), n.length && s.message && (n = [].concat(s.message)), n = n.map(complementError(s)), i.first && n.length) return f[s.field] = 1, t(n);
        if (r) {
          if (s.required && !e.value) return n = s.message ? [].concat(s.message).map(complementError(s)) : i.error ? [i.error(s, format(i.messages.required, s.field))] : [], t(n);
          let r = {};
          if (s.defaultField)
            for (const t in e.value) e.value.hasOwnProperty(t) && (r[t] = s.defaultField);
          r = {
            ...r,
            ...e.rule.fields
          };
          for (const e in r)
            if (r.hasOwnProperty(e)) {
              const t = Array.isArray(r[e]) ? r[e] : [r[e]];
              r[e] = t.map(a.bind(null, e))
            } const o = new Schema(r);
          o.messages(i.messages), e.rule.options && (e.rule.options.messages = i.messages, e.rule.options.error = i.error), o.validate(e.value, e.rule.options || i, e => {
            t(e && e.length ? n.concat(e) : e)
          })
        } else t(n)
      }
      r = r && (s.required || !s.required && e.value), s.field = e.field;
      const n = s.validator(s, e.value, o, e.source, i);
      n && n.then && n.then(() => o(), e => o(e))
    }, e => {
      ! function (e) {
        let t, s, r = [],
          a = {};
        for (t = 0; t < e.length; t++) o = e[t], Array.isArray(o) ? r = r.concat.apply(r, o) : r.push(o);
        var o;
        if (r.length)
          for (t = 0; t < r.length; t++) s = r[t].field, a[s] = a[s] || [], a[s].push(r[t]);
        else r = null, a = null;
        n(r, a)
      }(e)
    })
  },
  getType(e) {
    if (void 0 === e.type && e.pattern instanceof RegExp && (e.type = "pattern"), "function" != typeof e.validator && e.type && !validators.hasOwnProperty(e.type)) throw new Error(format("Unknown rule type %s", e.type));
    return e.type || "string"
  },
  getValidationMethod(e) {
    if ("function" == typeof e.validator) return e.validator;
    const t = Object.keys(e),
      s = t.indexOf("message");
    return -1 !== s && t.splice(s, 1), 1 === t.length && "required" === t[0] ? validators.required : validators[this.getType(e)] || !1
  }
}, Schema.register = function (e, t) {
  if ("function" != typeof t) throw new Error("Cannot register a validator by type, validator is not a function");
  validators[e] = t
}, Schema.messages = defaultMessages;
export default Schema;